package resml

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

// Http 类型的资源
type HttpResource struct {
	uri    *url.URL
	uriStr string
	meta   Meta // 在创建时主动设置的，则不需要再请求，用于更新资源；否则需要 Head 请求获取并不缓存。
}

// 创建 HttpResource 实例。
func NewHttpResource(uri *url.URL, meta Meta) *HttpResource {
	return &HttpResource{uri: uri, uriStr: uri.String(), meta: meta}
}

// 资源的 URI。
func (r *HttpResource) Uri() string {
	return r.uriStr
}

// 资源的元数据。如果创建 HttpResource 时设置了 meta，则直接返回；
// 否则每次调用会发送 HEAD 请求获取最新的元数据。
func (r *HttpResource) Meta() (Meta, error) {
	if r.meta != nil {
		return r.meta, nil // 创建 HttpResource 时主动设置的
	}
	req, err := http.NewRequest(http.MethodHead, r.Uri(), nil)
	if err != nil {
		return nil, err
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if err = r.statusError(http.MethodHead, res); err != nil {
		return nil, err
	}
	return MetaOfHttpHeader(res.Header)
}

// 获取资源
func (r *HttpResource) Get() (io.ReadCloser, Meta, error) {
	res, err := http.Get(r.Uri())
	if err != nil {
		return nil, nil, err
	}
	if err = r.statusError(http.MethodGet, res); err != nil {
		return nil, nil, err
	}
	meta, err := MetaOfHttpHeader(res.Header)
	return res.Body, meta, err
}

// 创建/更新资源。如果输入的 Meta 为 nil，则使用创建资源时指定的 Meta (如果有)。
func (r *HttpResource) Put(meta Meta, reader io.Reader) error {
	if meta == nil {
		if r.meta == nil {
			return ErrNoMeta
		}
		meta = r.meta
	}
	req, err := http.NewRequest(http.MethodPut, r.Uri(), reader)
	if err != nil {
		return err
	}
	MetaSetToHttpHeader(meta, req.Header, true)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if err = r.statusError(http.MethodPut, res); err != nil {
		return err
	}
	return nil
}

// 删除资源
func (r *HttpResource) Remove() error {
	req, err := http.NewRequest(http.MethodDelete, r.Uri(), nil)
	if err != nil {
		return err
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if err = r.statusError(http.MethodDelete, res); err != nil {
		return err
	}
	return nil
}

// 状态码错误
func (r *HttpResource) statusError(method string, res *http.Response) error {
	if res.StatusCode >= 300 || res.StatusCode < 200 {
		msg := "[Http-Resource]status error: %s %s faild, code: %d, body: %s"
		body, _ := io.ReadAll(res.Body)
		return fmt.Errorf(msg, method, r.Uri(), res.StatusCode, string(body))
	}
	return nil
}
